﻿using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using ATMCableCloud.CableCloudExceptions;
using ATMConst;
using Package;

namespace ATMCableCloud
{
	internal abstract class AtmNode : IATMNodeFunctions
	{
		protected AtmNode(IPAddress address,
		                  int portNumber,
		                  string name)
		{
			this.name = name;
			links = new ArrayList();
			tcpServer = new NodeTcpServer(address,
			                              portNumber) {parentNode = this};
		}
		public ArrayList links { get; private set; }
		public string name { get; private set; }
		internal NodeTcpServer tcpServer { get; private set; }
		public ThreadStart nodeTcpServer { get; private set; }
		public Thread nodeThread { get; private set; }
		public void stopListening()
		{
		}
		/* thread-handler for Node */
		public void startListening()
		{
			try
			{
				tcpServer.start();
			}
			catch (SocketException)
			{
				disconnectFromSystem();
			}			
		}
		public CellPackage getData()
		{
			var package = tcpServer.receivePackage(tcpServer.socket);
			if (!(package is CellPackage))
				; // send invalidpackage to peer 
			return package as CellPackage;
		}
		public void sendData(CellPackage cell, Socket handler)
		{
			try
			{
				foreach (PhysicalLink link in links)
					if (link.localPort == cell.link_port)
					{
						var newCell = new CellPackage(link.remotePort,
						                                      cell.data as ATMCell.AtmCell);
						Console.WriteLine(link.remoteEndPoint.tcpServer.socket.Connected);
						Console.WriteLine("port wyjsciowy={0}", newCell.link_port);
						link.remoteEndPoint.tcpServer.sendPackage(newCell);
						
						return;
					}
				throw new RecipientNotFound(cell);
			}
			catch (RecipientNotFound e)
			{
				Console.WriteLine(e);
			}
		}
		internal class NodeTcpServer : BasicTcpServer
		{
			public NodeTcpServer(IPAddress address, int port)
				: base(address, port, 1)
			{
				mainTcpPackageHandler = nodePackageHandler;
				acceptingHandler_ = nodeAcceptingCallback;
			}
			public bool checkIsConnected()
			{
				var zeroByte = new byte[1];
				var state = true;
				var oldState = socketHandler.Blocking;
				try
				{
					socketHandler.Blocking = false;
					socketHandler.Send(zeroByte, 0, 0);
					//still connected
				}
				catch (SocketException e)
				{
					if (!e.NativeErrorCode.Equals(Const.WSAEWOULDBLOCK))
					{
						Console.WriteLine(e);
						state = false;
					}
				}
				return state;
			}
			public AtmNode parentNode = null;
			private void nodePackageHandler(Package.Package package,
			                                IAsyncResult asyncResult,
			                                Socket socket)
			{
				socketHandler = socket;
				while (true)
				{
					if (package == null)
						return;
						if (package.code == Const.Code.CELL)
						{
							Console.WriteLine(System.Text.Encoding.ASCII.GetChars((package.data as ATMCell.AtmCell).data));
							//Console.ReadLine();
							parentNode.sendData(package as CellPackage, socket);
							
						}
						else
						{
							//todo: throw exception, send message
						}
						package = receivePackage(socket);
				}
			}
			public void sendPackage(Package.Package package)
			{
				sendPackage(package, socketHandler);
			}
			public Socket socketHandler { get; private set; }
			protected void nodeAcceptingCallback(IAsyncResult ar)
			{
				socketHandler = getConnectedSocket(ar);
				allDone.Set();
				mainTcpPackageHandler(receivePackage(socketHandler),
										ar,
										socketHandler);
			}
			protected override void peerDisconnectionHandler(Socket socket)
			{
				parentNode.disconnectFromSystem();
			}
		}
		internal class PhysicalLink
		{
			public PhysicalLink(int local_port,
			                    int remote_port,
			                    AtmNode remoteNode)
			{
				localPort = local_port;
				remotePort = remote_port;
				remoteEndPoint = remoteNode;
			}

			public int localPort { get; private set; }

			public int remotePort { get; private set; }

			public AtmNode remoteEndPoint { get; private set; }
		}
		public void connectToSystem()
		{
			nodeTcpServer = startListening;
			nodeThread = new Thread(nodeTcpServer) {IsBackground = true};
			nodeThread.Start();
		}
		public void disconnectFromSystem()
		{
			nodeThread.Abort();
			tcpServer.socket.Close();
			Console.WriteLine("zamknieto");
			ConnectedATMNodes.getInstance().deleteNode(this);
		}
	};
}